# -*- coding:utf-8 -*-
__author__ = 'Dragon Sun'
__date__ = '2025-07-14 17:46:07'

import datetime
from typing import Union, Optional

import pandas
import rpyc
from dsOKXv5.RESTful.api_helper import get_okx_api, OKXAPIv5
from dsOKXv5.data_types import 交易方向枚举, 交易模式枚举
from dsOKXv5.models import 持仓模型类, K线模型类
from dsPyLib.rpc.ds_rpc_service import DSRPCService

from src.conf.conf import G_最大单量, 获取应用配置


# @rpyc.service
class OKXv5Service(DSRPCService):

    def __init__(self, 交易模式: 交易模式枚举):
        super().__init__()

        self.交易模式 = 交易模式
        self.应用配置 = 获取应用配置(交易模式=self.交易模式, 说明='RPC服务')
        self.api: Optional[OKXAPIv5] = get_okx_api(交易模式=交易模式)

    @rpyc.exposed
    def m_获取近期K线(self, 数量: int) -> Union[Optional[pandas.DataFrame], Exception]:
        try:
            周期 = self.应用配置.K线周期
            之后数据时间 = 周期.时间转时间槽(时间=datetime.datetime.now(), 偏移=-(数量 + 1))  # 由于不包含未完成数据，所以偏移要多一个
            K线模型列表 = self.api.抓取历史K线(标的=self.应用配置.标的, 周期=self.应用配置.K线周期, 之后数据时间=之后数据时间)
            K线数据帧 = K线模型类.模型列表转数据帧(模型列表=K线模型列表)
            # # 将所有 Decimal 列转换为 float(在计算指标时没有针对Decimal进行处理，会报错)
            # K线数据帧[K线数据帧.select_dtypes(include=[Decimal]).columns] = K线数据帧.select_dtypes(include=[Decimal]).astype(float)
            # 时间槽(datetime64[ns])转成时间类型(Python datetime)(重要)
            # K线数据帧['时间槽'] = pandas.to_datetime(K线数据帧['时间槽'], format='%Y-%m-%d %H:%M')
            return K线数据帧
        except Exception as e:
            return e

    @rpyc.exposed
    def m_获取当前持仓(self) -> Union[Optional[持仓模型类], Exception]:
        try:
            持仓列表 = self.api.抓取当前持仓()
            持仓 = 持仓列表[0] if 持仓列表 else None  # 最多只返回一个持仓
            return 持仓
        except Exception as e:
            return e

    @rpyc.exposed
    def m_吃单(
            self, 产品: str, 方向: 交易方向枚举, 数量: float, 杠杆: float, 止盈触发价: float, 止损触发价: float
    ) -> Union[Optional[持仓模型类], Exception]:
        """
        :param 产品:
        :param 方向:
        :param 数量:
        :param 杠杆:
        :param 止盈触发价: 如果为0，则代表无止盈
        :param 止损触发价: 如果为0，则代表无止损
        :return:
        """
        try:
            # 传递进来的是rpyc.core.netref，需要转换一下
            方向 = 交易方向枚举(方向.value)

            # 设置杠杆
            开多杠杆倍数, 开空杠杆倍数 = self.api.抓取杠杆倍数(产品=产品)
            当前杠杆倍数 = 开多杠杆倍数 if 方向 == 交易方向枚举.多 else 开空杠杆倍数
            if 当前杠杆倍数 != 杠杆:
                self.api.设置杠杆倍数(产品=产品, 交易方向=方向, 杠杆倍数=杠杆)

            # 计算数量
            最大可买数, 最大可卖数 = self.api.抓取最大可下单数(产品=产品)
            最大交易数 = 最大可买数 if 方向 == 交易方向枚举.多 else 最大可卖数
            最大交易数 = int((最大交易数 - 0.1) * 100) / 100  # 少开0.1张，防止因价格变化导致最大交易数变少
            if 最大交易数 < 1.0:
                return ValueError('可交易数量太小：{最大交易数}')
            最大交易数 = min(G_最大单量, max(1.0, 最大交易数))  # 最大交易数在 [1.0, G_最大单量]
            交易数量 = min(最大交易数, 数量)

            # 开仓
            if 止盈触发价 == 0:
                止盈触发价 = None
            if 止损触发价 == 0:
                止损触发价 = None
            self.api.合约市价开仓(产品=产品, 方向=方向, 数量=交易数量, 止盈触发价=止盈触发价, 止损触发价=止损触发价)

            # 返回
            当前持仓 = self.m_获取当前持仓()
            return 当前持仓
        except Exception as e:
            return e

    @rpyc.exposed
    def m_反手(self, 止盈触发价: float, 止损触发价: float) -> Union[Optional[持仓模型类], Exception]:
        # 反手就是先市价全平，然后再反方向吃单(无止盈止损，需要尽快设置)

        # 获取当前持仓
        持仓 = self.m_获取当前持仓()
        if 持仓 is None:
            return ValueError('当前无持仓')
        if isinstance(持仓, Exception):
            return 持仓  # 这个时候是异常
        产品 = 持仓.标的
        数量 = 持仓.持仓量
        方向 = 持仓.方向
        方向 = 交易方向枚举.空 if 方向 == 交易方向枚举.多 else 交易方向枚举.多  # 反向

        # 平仓
        result = self.m_市价全平()
        if isinstance(result, Exception):
            return result

        # 获取杠杆
        try:
            开多杠杆倍数, 开空杠杆倍数 = self.api.抓取杠杆倍数(产品=产品)
        except Exception as e:
            return e
        杠杆 = 开多杠杆倍数 if 方向 == 交易方向枚举.多 else 开空杠杆倍数

        # 吃单
        新持仓 = self.m_吃单(产品=产品, 方向=方向, 数量=数量, 杠杆=杠杆, 止盈触发价=止盈触发价, 止损触发价=止损触发价)
        return 新持仓

    @rpyc.exposed
    def m_修改止盈触发价(self, 止盈触发价: float) -> Union[Optional[持仓模型类], Exception]:
        # 获取当前持仓信息
        result = self.m_获取当前持仓()
        if result is None:
            return ValueError('当前无持仓')
        if isinstance(result, Exception):
            return result
        持仓 = result
        产品 = 持仓.标的
        方向 = 持仓.方向

        # 如果无止盈止损则下单，否则修改
        if 持仓.止盈止损单 is None:
            # 当前无止盈止损
            try:
                self.api.仓位止盈止损下单(产品=产品, 方向=方向, 止盈触发价=止盈触发价, 止损触发价=None)
            except Exception as e:
                return e
        else:
            编号 = 持仓.止盈止损单.编号
            try:
                self.api.修改止盈触发价(产品=产品, 编号=编号, 止盈价=止盈触发价)
            except Exception as e:
                return e

        # 重新获取持仓
        新持仓 = self.m_获取当前持仓()
        return 新持仓

    @rpyc.exposed
    def m_修改止损触发价(self, 止损触发价: float) -> Union[Optional[持仓模型类], Exception]:
        # 获取当前持仓信息
        result = self.m_获取当前持仓()
        if result is None:
            return ValueError('当前无持仓')
        if isinstance(result, Exception):
            return result
        持仓 = result
        产品 = 持仓.标的
        方向 = 持仓.方向

        # 如果无止盈止损则下单，否则修改
        if 持仓.止盈止损单 is None:
            # 当前无止盈止损
            try:
                self.api.仓位止盈止损下单(产品=产品, 方向=方向, 止盈触发价=None, 止损触发价=止损触发价)
            except Exception as e:
                return e
        else:
            编号 = 持仓.止盈止损单.编号
            try:
                self.api.修改止损触发价(产品=产品, 编号=编号, 止损价=止损触发价)
            except Exception as e:
                return e

        # 重新获取持仓
        新持仓 = self.m_获取当前持仓()
        return 新持仓

    @rpyc.exposed
    def m_市价全平(self) -> Optional[Exception]:
        try:
            持仓 = self.m_获取当前持仓()
            if isinstance(持仓, Exception):
                return 持仓
            if 持仓 is None:
                return ValueError('当前无持仓')

            self.api.市价仓位全平(产品=持仓.标的, 持仓方向=持仓.方向)
            return None
        except Exception as e:
            return e
